<?php
/**
 * Created by PhpStorm.
 * User: linhongzhao
 * Date: 18-4-11
 * Time: 上午11:23
 */
namespace EFrame;

use MongoDB\BSON\ObjectId;
use MongoDB\Driver\BulkWrite;
use MongoDB\Driver\Exception\BulkWriteException;
use MongoDB\Driver\Manager;
use MongoDB\Driver\Query;
use MongoDB\Driver\WriteError;

class Mongo
{
    /**
     * @var \MongoDB\Driver\Manager
     */
    protected $_manager;
    /**
     * @var \MongoDB\Driver\Command
     */
    protected $_command;

    /**
     * @var string
     */
    protected $_db;
    /**
     * @var string
     */
    protected $_collection;

    /**
     * @var array
     */
    protected $_data = [];
    protected $_field = [];

    /**
     * @var int $_limit 为0时，mongo将返回所有数据
     */
    protected $_limit = 0;
    protected $_skip = 0;
    protected $_where = [];

    protected $_error = null;

    public function __construct(string $server, string $db, string $user = null, string $pwd = null, string $collection = null)
    {
        $uri = 'mongodb://' . $server . '/' . $db;
        $options = [];
        $options['username'] = $user;
        $options['password'] = $pwd;
        $this->_manager  = new Manager($uri, $options);
        if (!empty($db)) {
            $this->_db = $db;
        }
        if (!empty($collection)) {
            $this->_collection = $collection;
        }
    }

    /**
     * @param array $where
     * @return Mongo
     */
    public function where(array $where): Mongo
    {
        $this->_where = $where;
        return $this;
    }

    /**
     * 每个元素必须为字符串，非字符串元素将被忽略
     * @param array $fields
     * @return Mongo
     */
    public function field(array $fields): Mongo
    {
        foreach ($fields as $field) {
            if (is_string($field))
            {
                $this->_field[$field] = 1;
            }
        }
        return $this;
    }

    /**
     * @param int $limit
     * @param int $skip
     * @return Mongo
     */
    public function limit(int $limit, int $skip = 0): Mongo
    {
        if ($skip) {
            $this->_limit = $skip;
            $this->_skip = $limit;
        } else {
            $this->_limit = $limit;
        }
        return $this;
    }

    /**
     * 可以作为所有操作数据的缓存区
     * @param array $data
     * @param bool merge
     * @return Mongo
     * @see Mongo::insert()
     * @see Mongo::insertAll()
     */
    public function data(array $data, bool $push = false): Mongo
    {
        if ($push) {
            array_push($this->_data, $data);
        } else {
            $this->_data = $data;
        }
        return $this;
    }

    /**
     * @return \MongoDB\Driver\Cursor
     * @throws \MongoDB\Driver\Exception\Exception
     */
    public function select()
    {
        $options = [];
        if (!empty($this->_limit)) {
            $options['limit'] = $this->_limit;
        }
        if (!empty($this->_skip)) {
            $options['skip'] = $this->_skip;
        }
        if (!empty($this->_field)) {
            $options['projection'] = $this->_field;
        }
        $query = new Query($this->_where, $options);
        $namespace = $this->_db . '.' . $this->_collection;
        return $this->_manager->executeQuery($namespace, $query);
    }

    /**
     * @return int
     * @see \MongoDB\Driver\WriteResult::getDeletedCount()
     */
    public function delete(): int
    {
        $bulk = new BulkWrite;

        $options = [];
        if (!empty($this->_limit)) {
            $options['limit'] = $this->_limit;
        }
        if (!empty($this->_skip)) {
            $options['skip'] = $this->_skip;
        }
        $bulk->delete($this->_where, $options);
        $namespace = $this->_db . '.' . $this->_collection;
        try {
            $res =  $this->_manager->executeBulkWrite($namespace, $bulk);
            return $res->getDeletedCount();
        } catch (BulkWriteException $e) {
            $this->_error = $e->getWriteResult()->getWriteErrors();
            return null;
        }
    }

    /**
     * @param array $document 要插入的单条文档
     * @return array 返回所有插入成功的记录的id
     */
    public function insert(array $document = null): array
    {
        if (!empty($document)) {
            array_push($this->_data, $document);
        }
        $bulk = new BulkWrite;
        $ids = [];
        foreach ($this->_data as $data) {
            $res = $bulk->insert($data);
            if ($res) {
                $ids[] = $res;
            }
        }
        $namespace = $this->_db . '.' . $this->_collection;
        try {
            $this->_manager->executeBulkWrite($namespace, $bulk);
            return $ids;
        } catch (BulkWriteException $e) {
            $this->_error = $e->getWriteResult()->getWriteErrors();
            return null;
        }
    }

    /**
     * @param array $documents 要插入的文档列表
     * @return array
     * @see Mongo::insert()
     */
    public function insertAll(array $documents = null): array
    {
        if (!empty($documents)) {
            $this->_data = array_merge($this->_data, $documents);
        }
        return $this->insert();
    }

    /**
     * @return array
     */
    public function update(): array
    {
        $bulk = new BulkWrite;

        $options = [];
        $options['upsert'] = false;
        $options['multi'] = false;

        $set = [
            '$set' => $this->_data,
        ];

        $bulk->update($this->_where, $set, $options);
        $namespace = $this->_db . '.' . $this->_collection;
        try {
            $res =  $this->_manager->executeBulkWrite($namespace, $bulk);
            return $res->getUpsertedIds();
        } catch (BulkWriteException $e) {
            $this->_error = $e->getWriteResult()->getWriteErrors();
            return null;
        }
    }

    /**
     * 自动判断并执行
     * @return bool
     */
    protected function execute(): bool
    {
        return false;
    }

    /**
     * @param string $name
     * @return Mongo
     */
    public function collection(string $name): Mongo
    {
        $this->_collection = $name;
        return $this;
    }

    /**
     * @param string $name
     * @return Mongo
     * @see self::collection(string $name)
     */
    public function table(string $name): Mongo
    {
        return $this->collection($name);
    }

    /**
     * @return WriteError
     */
    public function error()
    {
        return $this->_error;
    }

    public function getData(): array
    {
        return $this->_data;
    }

    /**
     * 一般执行操作后调用以清空
     */
    protected function clean()
    {
        $this->_data = [];
        $this->_command = [];
        $this->_where = [];
        $this->_field = [];
        $this->_limit = 0;
        $this->_skip = 0;
    }

    /**
     * @return ObjectId
     * @see ObjectId
     */
    public static function getObjectId(): ObjectId
    {
        return new ObjectId;
    }

    /**
     * 将运算符转化为驱动能理解的运算符
     * @param string $ch
     * @return string
     */
    protected static function switchCmp(string $ch): string
    {
        switch ($ch) {
            case '>':
            case 'gt':
                return '$gt';
            case '>=':
            case 'gte':
                return '$gte';
            case '<':
            case 'lt':
                return '$lt';
            case '<=':
            case 'lte':
                return '$lte';
        }
        return $ch;
    }
}